﻿using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;

namespace client.models.Net
{
    public class NetClient : IDisposable
    {
        private TcpClient _client = null;
        private NetworkStream _stream = null;

        public async Task<bool> ConnectAsync(string host, int port, int timeoutSeconds)
        {
            if (_client != null)
            {
                _client.Dispose();
                _client = null;
            }

            _client = new TcpClient();
            var cts = new CancellationTokenSource();

            try
            {
                var timeoutTask = Task.Delay(TimeSpan.FromSeconds(timeoutSeconds), cts.Token);
                var connectTask = _client.ConnectAsync(host, port);
                var completedTask = await Task.WhenAny(connectTask, timeoutTask);

                // timeout
                if (completedTask == timeoutTask)
                {
                    _client?.Dispose();
                    _client = null;
                    return false;
                }

                cts.Cancel();
                await connectTask;
                _stream = _client.GetStream();
                // TODO handle read/write timeout
                //_stream.ReadTimeout = 5000;
                //_stream.WriteTimeout = 5000;
                return true;
            }
            catch (Exception)
            {
                // TODO logging
                _client?.Dispose();
                _client = null;
                return false;
            }
            finally
            {
                cts.Dispose();
            }
        }

        public void Dispose()
        {
            _stream?.Dispose();
            _client?.Dispose();
            _stream = null;
            _client = null;
        }

        public async Task<CommandResponse> SendRequestAsync(string cmd, object data)
        {
            string requestId = Guid.NewGuid().ToString();

            var request = new CommandRequest
            {
                cmd = cmd,
                id = requestId,
                data = data
            };

            string json = JsonConvert.SerializeObject(request);
            byte[] jsonBytes = Encoding.UTF8.GetBytes(json);
            // length is 4 bytes , little end.
            byte[] lengthBytes = BitConverter.GetBytes(jsonBytes.Length);
            if (!BitConverter.IsLittleEndian)// double check little end
            {
                Array.Reverse(lengthBytes);
            }

            await _stream.WriteAsync(lengthBytes, 0, lengthBytes.Length);
            await _stream.WriteAsync(jsonBytes, 0, jsonBytes.Length);
            return await ReceiveResponseAsync(requestId);
        }

        private async Task<CommandResponse> ReceiveResponseAsync(string expectedId)
        {
            byte[] lengthBytes = new byte[4];
            await _stream.ReadAsync(lengthBytes, 0, 4);
            int length = BitConverter.ToInt32(lengthBytes, 0);

            byte[] jsonBytes = new byte[length];
            await _stream.ReadAsync(jsonBytes, 0, length);
            string jsonResponse = Encoding.UTF8.GetString(jsonBytes);

            var response = JsonConvert.DeserializeObject<CommandResponse>(jsonResponse);
            return (response.id == expectedId) ? response : null;
        }
    }
}
